using System;
using System.Collections.Generic;
using System.IO;
using Spectre.Console.Internal;

namespace Spectre.Console
{
    /// <summary>
    /// Represents a FIGlet font.
    /// </summary>
    public sealed class FigletFont
    {
        private readonly Dictionary<int, FigletCharacter> _characters;
        private static readonly Lazy<FigletFont> _standard;

        /// <summary>
        /// Gets the number of characters in the font.
        /// </summary>
        public int Count => _characters.Count;

        /// <summary>
        /// Gets the height of the font.
        /// </summary>
        public int Height { get; }

        /// <summary>
        /// Gets the font's baseline.
        /// </summary>
        public int Baseline { get; }

        /// <summary>
        /// Gets the font's maximum width.
        /// </summary>
        public int MaxWidth { get; }

        /// <summary>
        /// Gets the default FIGlet font.
        /// </summary>
        public static FigletFont Default => _standard.Value;

        static FigletFont()
        {
            _standard = new Lazy<FigletFont>(() => Parse(ResourceReader.ReadManifestData("Spectre.Console/Figlet/Fonts/Standard.flf")));
        }

        internal FigletFont(IEnumerable<FigletCharacter> characters, FigletHeader header)
        {
            _characters = new Dictionary<int, FigletCharacter>();

            foreach (var character in characters)
            {
                if (_characters.ContainsKey(character.Code))
                {
                    throw new InvalidOperationException("Character already exist");
                }

                _characters[character.Code] = character;
            }

            Height = header.Height;
            Baseline = header.Baseline;
            MaxWidth = header.MaxLength;
        }

        /// <summary>
        /// Loads a FIGlet font from the specified stream.
        /// </summary>
        /// <param name="stream">The stream to load the FIGlet font from.</param>
        /// <returns>The loaded FIGlet font.</returns>
        public static FigletFont Load(Stream stream)
        {
            using (var reader = new StreamReader(stream))
            {
                return Parse(reader.ReadToEnd());
            }
        }

        /// <summary>
        /// Loads a FIGlet font from disk.
        /// </summary>
        /// <param name="path">The path of the FIGlet font to load.</param>
        /// <returns>The loaded FIGlet font.</returns>
        public static FigletFont Load(string path)
        {
            return Parse(File.ReadAllText(path));
        }

        /// <summary>
        /// Parses a FIGlet font from the specified <see cref="string"/>.
        /// </summary>
        /// <param name="source">The FIGlet font source.</param>
        /// <returns>The parsed FIGlet font.</returns>
        public static FigletFont Parse(string source)
        {
            return FigletFontParser.Parse(source);
        }

        internal int GetWidth(string text)
        {
            var width = 0;
            foreach (var character in text)
            {
                width += GetCharacter(character)?.Width ?? 0;
            }

            return width;
        }

        internal FigletCharacter? GetCharacter(char character)
        {
            _characters.TryGetValue(character, out var result);
            return result;
        }

        internal IEnumerable<FigletCharacter> GetCharacters(string text)
        {
            if (text is null)
            {
                throw new ArgumentNullException(nameof(text));
            }

            var result = new List<FigletCharacter>();
            foreach (var character in text)
            {
                if (_characters.TryGetValue(character, out var figletCharacter))
                {
                    result.Add(figletCharacter);
                }
            }

            return result;
        }
    }
}
